#include "RCompiler.h"

#include "llvm/Function.h"
#include "llvm/Instructions.h"
#include "llvm/Module.h"
#include "llvm/Pass.h"
#include "llvm/Support/CallSite.h"
#include "llvm/Target/TargetData.h"
#include "llvm/Analysis/InlineCost.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Constants.h"
#include "Stack.h"
#include "Evaluator.h"
#include "CVector.h"
#include "ReactorIntrinsics.h"

//#define DEBUG_STV
#if defined(DEBUG_STV)
# define stvddo(...) __VA_ARGS__
#else
# define stvddo(...)
#endif

using namespace llvm;

namespace R {

	class StackState {
	public:
		StackState* clone() { return this; }
	};

  class StackToValuePass : public FunctionPass {
  	private:
  public:
    static char ID;

		RCompiler* compiler;

		enum stackNumber {
			ANYSTACK,
			INTSTACK,
			CALLSTACK,
			NUMSTACKS };

		CVector<llvm::Value*>      *intStack;
		CVector<llvm::Value*>      *anyStack;
		CVector<llvm::Value*>      *callStack;
		CVector<llvm::BasicBlock*> *reached;

		BasicBlock* initialBB;
		Instruction* initialInst;

    StackToValuePass(RCompiler* comp) : FunctionPass(ID) {
    	initialBB = NULL;
    	intStack = NULL;
    	anyStack = NULL;
    	reached = NULL;
    	compiler = comp;
    }

    virtual const char* getPassName() const {
      return "transform stack accesses into value access";
    }

		bool isReached(llvm::BasicBlock *bb) {
			if(reached->has(bb) >= 0){
				return true;
			}
			reached->push(bb);
			return false;
		}

		bool df(llvm::BasicBlock &bb, uint64_t any_sp, uint64_t int_sp, uint64_t call_sp) {
			ReactorIntrinsics* intrinsics = compiler->getIntrinsics();
			bool changed = false;

			if(!isReached(&bb)) {
				for (BasicBlock::iterator ii=bb.begin(), ie=bb.end(); ii!=ie;) {
					Instruction *i = ii;
					ii++;
					if (i->getOpcode() != Instruction::Call &&
							i->getOpcode() != Instruction::Invoke) {
						continue;
					}

					CallSite call(i);
					Function* func = call.getCalledFunction();

					if(func == compiler->anyPush) {
						Value *v;
						if(anyStack->size() == any_sp) {
							anyStack->pushRoom();
							v = new llvm::AllocaInst(intrinsics->typeAny, "", initialInst);
							anyStack->set(v, any_sp);
							new llvm::StoreInst(
									llvm::ConstantExpr::getIntToPtr(llvm::ConstantInt::get(intrinsics->typeInt64, 0), intrinsics->typeAny),
									v,
									initialInst);
							Instruction* cast =
									new llvm::BitCastInst(v, intrinsics->ptrPtrType, "", initialInst);
							Value* GCArgs[2] = { cast, intrinsics->constantPtrNull };
							CallInst::Create(intrinsics->llvm_gc_gcroot, GCArgs, "", initialInst);

							stvddo(printf("CREATED NEW ALLOCA -> "); fflush(NULL); v->dump(); cast = new BitCastInst(v, intrinsics->ptrType, "", i); CallInst::Create(intrinsics->printObjectLLVM, cast, "", i);)
						}
						else{
							v = anyStack->get(any_sp);
						}
						StoreInst *si = new llvm::StoreInst(i->getOperand(1), v, i);

						stvddo(CallInst::Create(intrinsics->printIntLLVM, ConstantInt::get(IntegerType::getInt32Ty(compiler->selfModule->getContext()), 0), "", i);\
						CallInst::Create(intrinsics->printIntLLVM, ConstantInt::get(IntegerType::getInt32Ty(compiler->selfModule->getContext()), any_sp), "", i);\
						Instruction *cast = new BitCastInst(v, intrinsics->ptrType, "", i);\
						CallInst::Create(intrinsics->printObjectLLVM, cast, "", i);\
						cast = new BitCastInst(i->getOperand(1), intrinsics->ptrType, "", i);\
						CallInst::Create(intrinsics->printObjectLLVM, cast, "", i);\
						printf("\n<SP>  any push\n");\
						printf("CHANGED -> "); fflush(NULL); i->dump();\
						printf("INTO -> "); fflush(NULL); v->dump(); si->dump(); printf("\n");\
						printf("SP = "WORD"\n", any_sp);)

						any_sp++;
						i->eraseFromParent();
						changed = true;
					}

					else if(func == compiler->anyPop) {
						rerror_on(!isa<llvm::ConstantInt>(i->getOperand(1)), "anyStack.pop with non constant value found !");
						int pop_arg = dyn_cast<llvm::ConstantInt>(i->getOperand(1))->getZExtValue();
						any_sp -= pop_arg;
						Value *v = anyStack->get(any_sp);
						LoadInst *li = new llvm::LoadInst(v, "", i);

						stvddo(CallInst::Create(intrinsics->printIntLLVM, ConstantInt::get(IntegerType::getInt32Ty(compiler->selfModule->getContext()), 1), "", i);\
						CallInst::Create(intrinsics->printIntLLVM, ConstantInt::get(IntegerType::getInt32Ty(compiler->selfModule->getContext()), any_sp), "", i);\
						Instruction *cast = new BitCastInst(v, intrinsics->ptrType, "", i);\
						CallInst::Create(intrinsics->printObjectLLVM, cast, "", i);\
						cast = new BitCastInst(li, intrinsics->ptrType, "", i);\
						CallInst::Create(intrinsics->printObjectLLVM, cast, "", i);\
						printf("<SP>  any pop!!!!\n");\
						printf("CHANGED -> "); fflush(NULL); i->dump();\
						printf("INTO -> "); fflush(NULL); li->dump(); printf("\n");\
						printf("SP = "WORD"\n", any_sp);)

						i->replaceAllUsesWith(li);
						i->eraseFromParent();
						changed = true;
					}

					else if(func == compiler->anyPeek) {
						rerror_on(!isa<llvm::ConstantInt>(i->getOperand(1)),
								"in %s, anyStack.peek with non constant value found !", bb.getParent()->getName().data());
						int peek_arg = dyn_cast<llvm::ConstantInt>(i->getOperand(1))->getZExtValue();
						Value *&v = anyStack->get(any_sp - peek_arg - 1);
						LoadInst *li = new llvm::LoadInst(v, "", i);

						stvddo(CallInst::Create(intrinsics->printIntLLVM, ConstantInt::get(IntegerType::getInt32Ty(compiler->selfModule->getContext()), 2), "", i);\
						CallInst::Create(intrinsics->printIntLLVM, ConstantInt::get(IntegerType::getInt32Ty(compiler->selfModule->getContext()), any_sp - peek_arg - 1), "", i);\
						Instruction* cast = new BitCastInst(li, intrinsics->ptrType, "", i);\
						CallInst::Create(intrinsics->printObjectLLVM, cast, "", i);\
						printf("  any peek!!!!\n");\
						printf("CHANGED -> "); fflush(NULL); i->dump();\
						printf("INTO -> "); fflush(NULL); li->dump(); printf("\n");\
						printf("SP = "WORD"\n", any_sp);)

						i->replaceAllUsesWith(li);
						i->eraseFromParent();
						changed = true;
					}

					else if(func == compiler->anySet) {
						rerror_on(!isa<llvm::ConstantInt>(i->getOperand(1)), "anyStack.set with non constant value found !");
						llvm::ConstantInt *CI = dyn_cast<llvm::ConstantInt>(i->getOperand(1));
						rerror_on(!CI, "AnyStack::set has non constant int operand !");
						int set_arg = CI->getZExtValue();
						Value *v = anyStack->get(any_sp - set_arg - 1);
						StoreInst *si = new llvm::StoreInst(i->getOperand(2), v, i);

						stvddo(CallInst::Create(intrinsics->printIntLLVM, ConstantInt::get(IntegerType::getInt32Ty(compiler->selfModule->getContext()), 3), "", i);\
						CallInst::Create(intrinsics->printIntLLVM, ConstantInt::get(IntegerType::getInt32Ty(compiler->selfModule->getContext()), any_sp - set_arg - 1), "", i);\
						printf("  any set!!!! instruction:\n");\
						printf("  any set\tsize = "WORD"\tindex = "WORD"\n", anyStack->size(), anyStack->size() - set_arg - 1);\
						printf("CHANGED -> "); fflush(NULL); i->dump();\
						printf("INTO -> "); fflush(NULL); si->dump(); printf("\n");)

						i->eraseFromParent(); // or i->removeFromParent();
						changed = true;
					}

					else if(func == compiler->intPush) {
						Value *v;
						if(intStack->size() == int_sp) {
							intStack->pushRoom();
							v = new llvm::AllocaInst(intrinsics->typeInt64, "", initialInst);
							intStack->set(v, int_sp);
							stvddo(printf("CREATED NEW ALLOCA -> "); fflush(NULL); v->dump();)
						} else {
							v = intStack->get(int_sp);
						}
						StoreInst *si = new llvm::StoreInst(i->getOperand(1), v, i);
						int_sp++;

						stvddo(printf("<SP>  int push!!!!\n");\
						printf("CHANGED -> "); fflush(NULL); i->dump();\
						printf("INTO -> "); fflush(NULL); si->dump(); printf("\n");\
						printf("Int SP = "WORD"\n", intStack->size());)

						i->eraseFromParent(); // or i->removeFromParent();
						changed = true;
					}

					else if(func == compiler->intPop) {
						rerror_on(!isa<llvm::ConstantInt>(i->getOperand(1)), "intStack.pop with non constant value found !");
						llvm::ConstantInt *CI = dyn_cast<llvm::ConstantInt>(i->getOperand(1));
						rerror_on(!CI, "IntStack::pop has non constant int operand !");
						int pop_arg = CI->getZExtValue();
						int_sp -= pop_arg;
						Value *v = intStack->get(int_sp);
						LoadInst *li = new llvm::LoadInst(v, "", i);
						i->replaceAllUsesWith(li);
						changed = true;

						stvddo(printf("<SP>  int pop!!!!\n");\
						printf("CHANGED -> "); fflush(NULL); i->dump();\
						printf("INTO -> "); fflush(NULL); li->dump(); printf("\n");\
						printf("Int SP = "WORD"\n", intStack->size());)

						i->eraseFromParent(); // or i->removeFromParent();
					}

					else if(func == compiler->intPeek) {
						rerror_on(!isa<llvm::ConstantInt>(i->getOperand(1)), "intStack.peek with non constant value found !");
						int peek_arg = dyn_cast<llvm::ConstantInt>(i->getOperand(1))->getZExtValue();
						Value *v = intStack->get(int_sp - peek_arg - 1);
						LoadInst *li = new llvm::LoadInst(v, "", i);
						i->replaceAllUsesWith(li);
						changed = true;

						stvddo(printf("  int peek!!!!\n");\
						printf("CHANGED -> "); fflush(NULL); i->dump();\
						printf("INTO -> "); fflush(NULL); li->dump(); printf("\n");)

						i->eraseFromParent(); // or i->removeFromParent();
					}

					else if(func == compiler->intSet) {
						rerror_on(!isa<llvm::ConstantInt>(i->getOperand(1)), "intStack.set with non constant value found !");
						int set_arg = dyn_cast<llvm::ConstantInt>(i->getOperand(1))->getZExtValue();
						Value *v = intStack->get(int_sp - set_arg - 1);
						StoreInst *si = new llvm::StoreInst(i->getOperand(2), v, i);
						changed = true;

						stvddo(printf("  int set!!!!\n");\
						printf("CHANGED -> "); fflush(NULL); i->dump();\
						printf("INTO -> "); fflush(NULL); si->dump(); printf("\n");)

						i->eraseFromParent(); // or i->removeFromParent();
					}

					else {
						// For other calls made.
					}
				}

				llvm::TerminatorInst* t = bb.getTerminator();

				if(t) {
					uint64_t n = t->getNumSuccessors();

					for(uint64_t i=0; i<n; i++)
						changed |= df(*t->getSuccessor(i), any_sp, int_sp, call_sp);
				}
			}
			return changed;
		}

		virtual bool doInitialization(Module &M) {
			ReactorIntrinsics* intrinsics = compiler->getIntrinsics();
			intStack = new CVector<llvm::Value*>(64);
			anyStack = new CVector<llvm::Value*>(64);
//			callStack = new CVector<llvm::Value*>(64);
			reached  = new CVector<llvm::BasicBlock*>(64);
			return true;
		}

    virtual bool runOnFunction(Function &F) {
    	bool res = false;
    	uint64_t any_sp = 0;
    	uint64_t int_sp = 0;
    	uint64_t call_sp = 0;
    	initialBB = &(F.getEntryBlock());
    	initialInst = &initialBB->front();
    	doInitialization(*(F.getParent()));
    	jitprintf("\nBegin Stack To Value Pass on function %s\n", F.getName().data());
			res = df(F.getEntryBlock(), any_sp, int_sp, call_sp);
    	jitprintf("\nEnd Stack To Value Pass\n");
			doFinalization(*(F.getParent()));
			return res;
		}

    virtual bool doFinalization(Module &M) {
    	delete intStack;
    	delete anyStack;
//    	delete callStack;
    	delete reached;
    	return true;
    }

  private:
  };

  char StackToValuePass::ID = 0;

	FunctionPass* createStackToValuePass(RCompiler* compiler) {
		return new StackToValuePass(compiler);
	}

}

